/*
 * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package java.security;

import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.io.*;
import java.net.URL;
import sun.security.util.Debug;
import sun.security.util.PropertyExpander;

import sun.security.jca.*;

/**
 * <p>This class centralizes all security properties and common security
 * methods. One of its primary uses is to manage providers.
 *
 * <p>The default values of security properties are read from an
 * implementation-specific location, which is typically the properties file
 * {@code lib/security/java.security} in the Java installation directory.
 *
 * @author Benjamin Renaud
 */

public final class Security {

  /* Are we debugging? -- for developers */
  private static final Debug sdebug =
      Debug.getInstance("properties");

  /* The java.security properties */
  private static Properties props;

  // An element in the cache
  private static class ProviderProperty {

    String className;
    Provider provider;
  }

  static {
    // doPrivileged here because there are multiple
    // things in initialize that might require privs.
    // (the FileInputStream call and the File.exists call,
    // the securityPropFile call, etc)
    AccessController.doPrivileged(new PrivilegedAction<Void>() {
      public Void run() {
        initialize();
        return null;
      }
    });
  }

  private static void initialize() {
    props = new Properties();
    boolean loadedProps = false;
    boolean overrideAll = false;

    // first load the system properties file
    // to determine the value of security.overridePropertiesFile
    File propFile = securityPropFile("java.security");
    if (propFile.exists()) {
      InputStream is = null;
      try {
        FileInputStream fis = new FileInputStream(propFile);
        is = new BufferedInputStream(fis);
        props.load(is);
        loadedProps = true;

        if (sdebug != null) {
          sdebug.println("reading security properties file: " +
              propFile);
        }
      } catch (IOException e) {
        if (sdebug != null) {
          sdebug.println("unable to load security properties from " +
              propFile);
          e.printStackTrace();
        }
      } finally {
        if (is != null) {
          try {
            is.close();
          } catch (IOException ioe) {
            if (sdebug != null) {
              sdebug.println("unable to close input stream");
            }
          }
        }
      }
    }

    if ("true".equalsIgnoreCase(props.getProperty
        ("security.overridePropertiesFile"))) {

      String extraPropFile = System.getProperty
          ("java.security.properties");
      if (extraPropFile != null && extraPropFile.startsWith("=")) {
        overrideAll = true;
        extraPropFile = extraPropFile.substring(1);
      }

      if (overrideAll) {
        props = new Properties();
        if (sdebug != null) {
          sdebug.println
              ("overriding other security properties files!");
        }
      }

      // now load the user-specified file so its values
      // will win if they conflict with the earlier values
      if (extraPropFile != null) {
        BufferedInputStream bis = null;
        try {
          URL propURL;

          extraPropFile = PropertyExpander.expand(extraPropFile);
          propFile = new File(extraPropFile);
          if (propFile.exists()) {
            propURL = new URL
                ("file:" + propFile.getCanonicalPath());
          } else {
            propURL = new URL(extraPropFile);
          }
          bis = new BufferedInputStream(propURL.openStream());
          props.load(bis);
          loadedProps = true;

          if (sdebug != null) {
            sdebug.println("reading security properties file: " +
                propURL);
            if (overrideAll) {
              sdebug.println
                  ("overriding other security properties files!");
            }
          }
        } catch (Exception e) {
          if (sdebug != null) {
            sdebug.println
                ("unable to load security properties from " +
                    extraPropFile);
            e.printStackTrace();
          }
        } finally {
          if (bis != null) {
            try {
              bis.close();
            } catch (IOException ioe) {
              if (sdebug != null) {
                sdebug.println("unable to close input stream");
              }
            }
          }
        }
      }
    }

    if (!loadedProps) {
      initializeStatic();
      if (sdebug != null) {
        sdebug.println("unable to load security properties " +
            "-- using defaults");
      }
    }

  }

  /*
   * Initialize to default values, if <java.home>/lib/java.security
   * is not found.
   */
  private static void initializeStatic() {
    props.put("security.provider.1", "sun.security.provider.Sun");
    props.put("security.provider.2", "sun.security.rsa.SunRsaSign");
    props.put("security.provider.3", "com.sun.net.ssl.internal.ssl.Provider");
    props.put("security.provider.4", "com.sun.crypto.provider.SunJCE");
    props.put("security.provider.5", "sun.security.jgss.SunProvider");
    props.put("security.provider.6", "com.sun.security.sasl.Provider");
  }

  /**
   * Don't let anyone instantiate this.
   */
  private Security() {
  }

  private static File securityPropFile(String filename) {
    // maybe check for a system property which will specify where to
    // look. Someday.
    String sep = File.separator;
    return new File(System.getProperty("java.home") + sep + "lib" + sep +
        "security" + sep + filename);
  }

  /**
   * Looks up providers, and returns the property (and its associated
   * provider) mapping the key, if any.
   * The order in which the providers are looked up is the
   * provider-preference order, as specificed in the security
   * properties file.
   */
  private static ProviderProperty getProviderProperty(String key) {
    ProviderProperty entry = null;

    List<Provider> providers = Providers.getProviderList().providers();
    for (int i = 0; i < providers.size(); i++) {

      String matchKey = null;
      Provider prov = providers.get(i);
      String prop = prov.getProperty(key);

      if (prop == null) {
        // Is there a match if we do a case-insensitive property name
        // comparison? Let's try ...
        for (Enumeration<Object> e = prov.keys();
            e.hasMoreElements() && prop == null; ) {
          matchKey = (String) e.nextElement();
          if (key.equalsIgnoreCase(matchKey)) {
            prop = prov.getProperty(matchKey);
            break;
          }
        }
      }

      if (prop != null) {
        ProviderProperty newEntry = new ProviderProperty();
        newEntry.className = prop;
        newEntry.provider = prov;
        return newEntry;
      }
    }

    return entry;
  }

  /**
   * Returns the property (if any) mapping the key for the given provider.
   */
  private static String getProviderProperty(String key, Provider provider) {
    String prop = provider.getProperty(key);
    if (prop == null) {
      // Is there a match if we do a case-insensitive property name
      // comparison? Let's try ...
      for (Enumeration<Object> e = provider.keys();
          e.hasMoreElements() && prop == null; ) {
        String matchKey = (String) e.nextElement();
        if (key.equalsIgnoreCase(matchKey)) {
          prop = provider.getProperty(matchKey);
          break;
        }
      }
    }
    return prop;
  }

  /**
   * Gets a specified property for an algorithm. The algorithm name
   * should be a standard name. See the <a href=
   * "{@docRoot}/../technotes/guides/security/StandardNames.html">
   * Java Cryptography Architecture Standard Algorithm Name Documentation</a>
   * for information about standard algorithm names.
   *
   * One possible use is by specialized algorithm parsers, which may map
   * classes to algorithms which they understand (much like Key parsers
   * do).
   *
   * @param algName the algorithm name.
   * @param propName the name of the property to get.
   * @return the value of the specified property.
   * @deprecated This method used to return the value of a proprietary property in the master file
   * of the "SUN" Cryptographic Service Provider in order to determine how to parse
   * algorithm-specific parameters. Use the new provider-based and algorithm-independent {@code
   * AlgorithmParameters} and {@code KeyFactory} engine classes (introduced in the J2SE version 1.2
   * platform) instead.
   */
  @Deprecated
  public static String getAlgorithmProperty(String algName,
      String propName) {
    ProviderProperty entry = getProviderProperty("Alg." + propName
        + "." + algName);
    if (entry != null) {
      return entry.className;
    } else {
      return null;
    }
  }

  /**
   * Adds a new provider, at a specified position. The position is
   * the preference order in which providers are searched for
   * requested algorithms.  The position is 1-based, that is,
   * 1 is most preferred, followed by 2, and so on.
   *
   * <p>If the given provider is installed at the requested position,
   * the provider that used to be at that position, and all providers
   * with a position greater than {@code position}, are shifted up
   * one position (towards the end of the list of installed providers).
   *
   * <p>A provider cannot be added if it is already installed.
   *
   * <p>If there is a security manager, the
   * {@link java.lang.SecurityManager#checkSecurityAccess} method is called
   * with the {@code "insertProvider"} permission target name to see if
   * it's ok to add a new provider. If this permission check is denied,
   * {@code checkSecurityAccess} is called again with the
   * {@code "insertProvider."+provider.getName()} permission target name. If
   * both checks are denied, a {@code SecurityException} is thrown.
   *
   * @param provider the provider to be added.
   * @param position the preference position that the caller would like for this provider.
   * @return the actual preference position in which the provider was added, or -1 if the provider
   * was not added because it is already installed.
   * @throws NullPointerException if provider is null
   * @throws SecurityException if a security manager exists and its {@link
   * java.lang.SecurityManager#checkSecurityAccess} method denies access to add a new provider
   * @see #getProvider
   * @see #removeProvider
   * @see java.security.SecurityPermission
   */
  public static synchronized int insertProviderAt(Provider provider,
      int position) {
    String providerName = provider.getName();
    checkInsertProvider(providerName);
    ProviderList list = Providers.getFullProviderList();
    ProviderList newList = ProviderList.insertAt(list, provider, position - 1);
    if (list == newList) {
      return -1;
    }
    Providers.setProviderList(newList);
    return newList.getIndex(providerName) + 1;
  }

  /**
   * Adds a provider to the next position available.
   *
   * <p>If there is a security manager, the
   * {@link java.lang.SecurityManager#checkSecurityAccess} method is called
   * with the {@code "insertProvider"} permission target name to see if
   * it's ok to add a new provider. If this permission check is denied,
   * {@code checkSecurityAccess} is called again with the
   * {@code "insertProvider."+provider.getName()} permission target name. If
   * both checks are denied, a {@code SecurityException} is thrown.
   *
   * @param provider the provider to be added.
   * @return the preference position in which the provider was added, or -1 if the provider was not
   * added because it is already installed.
   * @throws NullPointerException if provider is null
   * @throws SecurityException if a security manager exists and its {@link
   * java.lang.SecurityManager#checkSecurityAccess} method denies access to add a new provider
   * @see #getProvider
   * @see #removeProvider
   * @see java.security.SecurityPermission
   */
  public static int addProvider(Provider provider) {
        /*
         * We can't assign a position here because the statically
         * registered providers may not have been installed yet.
         * insertProviderAt() will fix that value after it has
         * loaded the static providers.
         */
    return insertProviderAt(provider, 0);
  }

  /**
   * Removes the provider with the specified name.
   *
   * <p>When the specified provider is removed, all providers located
   * at a position greater than where the specified provider was are shifted
   * down one position (towards the head of the list of installed
   * providers).
   *
   * <p>This method returns silently if the provider is not installed or
   * if name is null.
   *
   * <p>First, if there is a security manager, its
   * {@code checkSecurityAccess}
   * method is called with the string {@code "removeProvider."+name}
   * to see if it's ok to remove the provider.
   * If the default implementation of {@code checkSecurityAccess}
   * is used (i.e., that method is not overriden), then this will result in
   * a call to the security manager's {@code checkPermission} method
   * with a {@code SecurityPermission("removeProvider."+name)}
   * permission.
   *
   * @param name the name of the provider to remove.
   * @throws SecurityException if a security manager exists and its {@link
   * java.lang.SecurityManager#checkSecurityAccess} method denies access to remove the provider
   * @see #getProvider
   * @see #addProvider
   */
  public static synchronized void removeProvider(String name) {
    check("removeProvider." + name);
    ProviderList list = Providers.getFullProviderList();
    ProviderList newList = ProviderList.remove(list, name);
    Providers.setProviderList(newList);
  }

  /**
   * Returns an array containing all the installed providers. The order of
   * the providers in the array is their preference order.
   *
   * @return an array of all the installed providers.
   */
  public static Provider[] getProviders() {
    return Providers.getFullProviderList().toArray();
  }

  /**
   * Returns the provider installed with the specified name, if
   * any. Returns null if no provider with the specified name is
   * installed or if name is null.
   *
   * @param name the name of the provider to get.
   * @return the provider of the specified name.
   * @see #removeProvider
   * @see #addProvider
   */
  public static Provider getProvider(String name) {
    return Providers.getProviderList().getProvider(name);
  }

  /**
   * Returns an array containing all installed providers that satisfy the
   * specified selection criterion, or null if no such providers have been
   * installed. The returned providers are ordered
   * according to their
   * {@linkplain #insertProviderAt(java.security.Provider, int) preference order}.
   *
   * <p> A cryptographic service is always associated with a particular
   * algorithm or type. For example, a digital signature service is
   * always associated with a particular algorithm (e.g., DSA),
   * and a CertificateFactory service is always associated with
   * a particular certificate type (e.g., X.509).
   *
   * <p>The selection criterion must be specified in one of the following two
   * formats:
   * <ul>
   * <li> <i>{@literal <crypto_service>.<algorithm_or_type>}</i>
   * <p> The cryptographic service name must not contain any dots.
   * <p> A
   * provider satisfies the specified selection criterion iff the provider
   * implements the
   * specified algorithm or type for the specified cryptographic service.
   * <p> For example, "CertificateFactory.X.509"
   * would be satisfied by any provider that supplied
   * a CertificateFactory implementation for X.509 certificates.
   * <li> <i>{@literal <crypto_service>.<algorithm_or_type>
   * <attribute_name>:<attribute_value>}</i>
   * <p> The cryptographic service name must not contain any dots. There
   * must be one or more space characters between the
   * <i>{@literal <algorithm_or_type>}</i> and the
   * <i>{@literal <attribute_name>}</i>.
   * <p> A provider satisfies this selection criterion iff the
   * provider implements the specified algorithm or type for the specified
   * cryptographic service and its implementation meets the
   * constraint expressed by the specified attribute name/value pair.
   * <p> For example, "Signature.SHA1withDSA KeySize:1024" would be
   * satisfied by any provider that implemented
   * the SHA1withDSA signature algorithm with a keysize of 1024 (or larger).
   *
   * </ul>
   *
   * <p> See the <a href=
   * "{@docRoot}/../technotes/guides/security/StandardNames.html">
   * Java Cryptography Architecture Standard Algorithm Name Documentation</a>
   * for information about standard cryptographic service names, standard
   * algorithm names and standard attribute names.
   *
   * @param filter the criterion for selecting providers. The filter is case-insensitive.
   * @return all the installed providers that satisfy the selection criterion, or null if no such
   * providers have been installed.
   * @throws InvalidParameterException if the filter is not in the required format
   * @throws NullPointerException if filter is null
   * @see #getProviders(java.util.Map)
   * @since 1.3
   */
  public static Provider[] getProviders(String filter) {
    String key = null;
    String value = null;
    int index = filter.indexOf(':');

    if (index == -1) {
      key = filter;
      value = "";
    } else {
      key = filter.substring(0, index);
      value = filter.substring(index + 1);
    }

    Hashtable<String, String> hashtableFilter = new Hashtable<>(1);
    hashtableFilter.put(key, value);

    return (getProviders(hashtableFilter));
  }

  /**
   * Returns an array containing all installed providers that satisfy the
   * specified* selection criteria, or null if no such providers have been
   * installed. The returned providers are ordered
   * according to their
   * {@linkplain #insertProviderAt(java.security.Provider, int)
   * preference order}.
   *
   * <p>The selection criteria are represented by a map.
   * Each map entry represents a selection criterion.
   * A provider is selected iff it satisfies all selection
   * criteria. The key for any entry in such a map must be in one of the
   * following two formats:
   * <ul>
   * <li> <i>{@literal <crypto_service>.<algorithm_or_type>}</i>
   * <p> The cryptographic service name must not contain any dots.
   * <p> The value associated with the key must be an empty string.
   * <p> A provider
   * satisfies this selection criterion iff the provider implements the
   * specified algorithm or type for the specified cryptographic service.
   * <li>  <i>{@literal <crypto_service>}.
   * {@literal <algorithm_or_type> <attribute_name>}</i>
   * <p> The cryptographic service name must not contain any dots. There
   * must be one or more space characters between the
   * <i>{@literal <algorithm_or_type>}</i>
   * and the <i>{@literal <attribute_name>}</i>.
   * <p> The value associated with the key must be a non-empty string.
   * A provider satisfies this selection criterion iff the
   * provider implements the specified algorithm or type for the specified
   * cryptographic service and its implementation meets the
   * constraint expressed by the specified attribute name/value pair.
   * </ul>
   *
   * <p> See the <a href=
   * "../../../technotes/guides/security/StandardNames.html">
   * Java Cryptography Architecture Standard Algorithm Name Documentation</a>
   * for information about standard cryptographic service names, standard
   * algorithm names and standard attribute names.
   *
   * @param filter the criteria for selecting providers. The filter is case-insensitive.
   * @return all the installed providers that satisfy the selection criteria, or null if no such
   * providers have been installed.
   * @throws InvalidParameterException if the filter is not in the required format
   * @throws NullPointerException if filter is null
   * @see #getProviders(java.lang.String)
   * @since 1.3
   */
  public static Provider[] getProviders(Map<String, String> filter) {
    // Get all installed providers first.
    // Then only return those providers who satisfy the selection criteria.
    Provider[] allProviders = Security.getProviders();
    Set<String> keySet = filter.keySet();
    LinkedHashSet<Provider> candidates = new LinkedHashSet<>(5);

    // Returns all installed providers
    // if the selection criteria is null.
    if ((keySet == null) || (allProviders == null)) {
      return allProviders;
    }

    boolean firstSearch = true;

    // For each selection criterion, remove providers
    // which don't satisfy the criterion from the candidate set.
    for (Iterator<String> ite = keySet.iterator(); ite.hasNext(); ) {
      String key = ite.next();
      String value = filter.get(key);

      LinkedHashSet<Provider> newCandidates = getAllQualifyingCandidates(key, value,
          allProviders);
      if (firstSearch) {
        candidates = newCandidates;
        firstSearch = false;
      }

      if ((newCandidates != null) && !newCandidates.isEmpty()) {
        // For each provider in the candidates set, if it
        // isn't in the newCandidate set, we should remove
        // it from the candidate set.
        for (Iterator<Provider> cansIte = candidates.iterator();
            cansIte.hasNext(); ) {
          Provider prov = cansIte.next();
          if (!newCandidates.contains(prov)) {
            cansIte.remove();
          }
        }
      } else {
        candidates = null;
        break;
      }
    }

    if ((candidates == null) || (candidates.isEmpty())) {
      return null;
    }

    Object[] candidatesArray = candidates.toArray();
    Provider[] result = new Provider[candidatesArray.length];

    for (int i = 0; i < result.length; i++) {
      result[i] = (Provider) candidatesArray[i];
    }

    return result;
  }

  // Map containing cached Spi Class objects of the specified type
  private static final Map<String, Class<?>> spiMap =
      new ConcurrentHashMap<>();

  /**
   * Return the Class object for the given engine type
   * (e.g. "MessageDigest"). Works for Spis in the java.security package
   * only.
   */
  private static Class<?> getSpiClass(String type) {
    Class<?> clazz = spiMap.get(type);
    if (clazz != null) {
      return clazz;
    }
    try {
      clazz = Class.forName("java.security." + type + "Spi");
      spiMap.put(type, clazz);
      return clazz;
    } catch (ClassNotFoundException e) {
      throw new AssertionError("Spi class not found", e);
    }
  }

  /*
   * Returns an array of objects: the first object in the array is
   * an instance of an implementation of the requested algorithm
   * and type, and the second object in the array identifies the provider
   * of that implementation.
   * The {@code provider} argument can be null, in which case all
   * configured providers will be searched in order of preference.
   */
  static Object[] getImpl(String algorithm, String type, String provider)
      throws NoSuchAlgorithmException, NoSuchProviderException {
    if (provider == null) {
      return GetInstance.getInstance
          (type, getSpiClass(type), algorithm).toArray();
    } else {
      return GetInstance.getInstance
          (type, getSpiClass(type), algorithm, provider).toArray();
    }
  }

  static Object[] getImpl(String algorithm, String type, String provider,
      Object params) throws NoSuchAlgorithmException,
      NoSuchProviderException, InvalidAlgorithmParameterException {
    if (provider == null) {
      return GetInstance.getInstance
          (type, getSpiClass(type), algorithm, params).toArray();
    } else {
      return GetInstance.getInstance
          (type, getSpiClass(type), algorithm, params, provider).toArray();
    }
  }

  /*
   * Returns an array of objects: the first object in the array is
   * an instance of an implementation of the requested algorithm
   * and type, and the second object in the array identifies the provider
   * of that implementation.
   * The {@code provider} argument cannot be null.
   */
  static Object[] getImpl(String algorithm, String type, Provider provider)
      throws NoSuchAlgorithmException {
    return GetInstance.getInstance
        (type, getSpiClass(type), algorithm, provider).toArray();
  }

  static Object[] getImpl(String algorithm, String type, Provider provider,
      Object params) throws NoSuchAlgorithmException,
      InvalidAlgorithmParameterException {
    return GetInstance.getInstance
        (type, getSpiClass(type), algorithm, params, provider).toArray();
  }

  /**
   * Gets a security property value.
   *
   * <p>First, if there is a security manager, its
   * {@code checkPermission}  method is called with a
   * {@code java.security.SecurityPermission("getProperty."+key)}
   * permission to see if it's ok to retrieve the specified
   * security property value..
   *
   * @param key the key of the property being retrieved.
   * @return the value of the security property corresponding to key.
   * @throws SecurityException if a security manager exists and its {@link
   * java.lang.SecurityManager#checkPermission} method denies access to retrieve the specified
   * security property value
   * @throws NullPointerException is key is null
   * @see #setProperty
   * @see java.security.SecurityPermission
   */
  public static String getProperty(String key) {
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
      sm.checkPermission(new SecurityPermission("getProperty." +
          key));
    }
    String name = props.getProperty(key);
    if (name != null) {
      name = name.trim(); // could be a class name with trailing ws
    }
    return name;
  }

  /**
   * Sets a security property value.
   *
   * <p>First, if there is a security manager, its
   * {@code checkPermission} method is called with a
   * {@code java.security.SecurityPermission("setProperty."+key)}
   * permission to see if it's ok to set the specified
   * security property value.
   *
   * @param key the name of the property to be set.
   * @param datum the value of the property to be set.
   * @throws SecurityException if a security manager exists and its {@link
   * java.lang.SecurityManager#checkPermission} method denies access to set the specified security
   * property value
   * @throws NullPointerException if key or datum is null
   * @see #getProperty
   * @see java.security.SecurityPermission
   */
  public static void setProperty(String key, String datum) {
    check("setProperty." + key);
    props.put(key, datum);
    invalidateSMCache(key);  /* See below. */
  }

  /*
   * Implementation detail:  If the property we just set in
   * setProperty() was either "package.access" or
   * "package.definition", we need to signal to the SecurityManager
   * class that the value has just changed, and that it should
   * invalidate it's local cache values.
   *
   * Rather than create a new API entry for this function,
   * we use reflection to set a private variable.
   */
  private static void invalidateSMCache(String key) {

    final boolean pa = key.equals("package.access");
    final boolean pd = key.equals("package.definition");

    if (pa || pd) {
      AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {
          try {
                        /* Get the class via the bootstrap class loader. */
            Class<?> cl = Class.forName(
                "java.lang.SecurityManager", false, null);
            Field f = null;
            boolean accessible = false;

            if (pa) {
              f = cl.getDeclaredField("packageAccessValid");
              accessible = f.isAccessible();
              f.setAccessible(true);
            } else {
              f = cl.getDeclaredField("packageDefinitionValid");
              accessible = f.isAccessible();
              f.setAccessible(true);
            }
            f.setBoolean(f, false);
            f.setAccessible(accessible);
          } catch (Exception e1) {
                        /* If we couldn't get the class, it hasn't
                         * been loaded yet.  If there is no such
                         * field, we shouldn't try to set it.  There
                         * shouldn't be a security execption, as we
                         * are loaded by boot class loader, and we
                         * are inside a doPrivileged() here.
                         *
                         * NOOP: don't do anything...
                         */
          }
          return null;
        }  /* run */
      });  /* PrivilegedAction */
    }  /* if */
  }

  private static void check(String directive) {
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
      security.checkSecurityAccess(directive);
    }
  }

  private static void checkInsertProvider(String name) {
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
      try {
        security.checkSecurityAccess("insertProvider");
      } catch (SecurityException se1) {
        try {
          security.checkSecurityAccess("insertProvider." + name);
        } catch (SecurityException se2) {
          // throw first exception, but add second to suppressed
          se1.addSuppressed(se2);
          throw se1;
        }
      }
    }
  }

  /*
  * Returns all providers who satisfy the specified
  * criterion.
  */
  private static LinkedHashSet<Provider> getAllQualifyingCandidates(
      String filterKey,
      String filterValue,
      Provider[] allProviders) {
    String[] filterComponents = getFilterComponents(filterKey,
        filterValue);

    // The first component is the service name.
    // The second is the algorithm name.
    // If the third isn't null, that is the attrinute name.
    String serviceName = filterComponents[0];
    String algName = filterComponents[1];
    String attrName = filterComponents[2];

    return getProvidersNotUsingCache(serviceName, algName, attrName,
        filterValue, allProviders);
  }

  private static LinkedHashSet<Provider> getProvidersNotUsingCache(
      String serviceName,
      String algName,
      String attrName,
      String filterValue,
      Provider[] allProviders) {
    LinkedHashSet<Provider> candidates = new LinkedHashSet<>(5);
    for (int i = 0; i < allProviders.length; i++) {
      if (isCriterionSatisfied(allProviders[i], serviceName,
          algName,
          attrName, filterValue)) {
        candidates.add(allProviders[i]);
      }
    }
    return candidates;
  }

  /*
   * Returns true if the given provider satisfies
   * the selection criterion key:value.
   */
  private static boolean isCriterionSatisfied(Provider prov,
      String serviceName,
      String algName,
      String attrName,
      String filterValue) {
    String key = serviceName + '.' + algName;

    if (attrName != null) {
      key += ' ' + attrName;
    }
    // Check whether the provider has a property
    // whose key is the same as the given key.
    String propValue = getProviderProperty(key, prov);

    if (propValue == null) {
      // Check whether we have an alias instead
      // of a standard name in the key.
      String standardName = getProviderProperty("Alg.Alias." +
              serviceName + "." +
              algName,
          prov);
      if (standardName != null) {
        key = serviceName + "." + standardName;

        if (attrName != null) {
          key += ' ' + attrName;
        }

        propValue = getProviderProperty(key, prov);
      }

      if (propValue == null) {
        // The provider doesn't have the given
        // key in its property list.
        return false;
      }
    }

    // If the key is in the format of:
    // <crypto_service>.<algorithm_or_type>,
    // there is no need to check the value.

    if (attrName == null) {
      return true;
    }

    // If we get here, the key must be in the
    // format of <crypto_service>.<algorithm_or_provider> <attribute_name>.
    if (isStandardAttr(attrName)) {
      return isConstraintSatisfied(attrName, filterValue, propValue);
    } else {
      return filterValue.equalsIgnoreCase(propValue);
    }
  }

  /*
   * Returns true if the attribute is a standard attribute;
   * otherwise, returns false.
   */
  private static boolean isStandardAttr(String attribute) {
    // For now, we just have two standard attributes:
    // KeySize and ImplementedIn.
    if (attribute.equalsIgnoreCase("KeySize")) {
      return true;
    }

    if (attribute.equalsIgnoreCase("ImplementedIn")) {
      return true;
    }

    return false;
  }

  /*
   * Returns true if the requested attribute value is supported;
   * otherwise, returns false.
   */
  private static boolean isConstraintSatisfied(String attribute,
      String value,
      String prop) {
    // For KeySize, prop is the max key size the
    // provider supports for a specific <crypto_service>.<algorithm>.
    if (attribute.equalsIgnoreCase("KeySize")) {
      int requestedSize = Integer.parseInt(value);
      int maxSize = Integer.parseInt(prop);
      if (requestedSize <= maxSize) {
        return true;
      } else {
        return false;
      }
    }

    // For Type, prop is the type of the implementation
    // for a specific <crypto service>.<algorithm>.
    if (attribute.equalsIgnoreCase("ImplementedIn")) {
      return value.equalsIgnoreCase(prop);
    }

    return false;
  }

  static String[] getFilterComponents(String filterKey, String filterValue) {
    int algIndex = filterKey.indexOf('.');

    if (algIndex < 0) {
      // There must be a dot in the filter, and the dot
      // shouldn't be at the beginning of this string.
      throw new InvalidParameterException("Invalid filter");
    }

    String serviceName = filterKey.substring(0, algIndex);
    String algName = null;
    String attrName = null;

    if (filterValue.length() == 0) {
      // The filterValue is an empty string. So the filterKey
      // should be in the format of <crypto_service>.<algorithm_or_type>.
      algName = filterKey.substring(algIndex + 1).trim();
      if (algName.length() == 0) {
        // There must be a algorithm or type name.
        throw new InvalidParameterException("Invalid filter");
      }
    } else {
      // The filterValue is a non-empty string. So the filterKey must be
      // in the format of
      // <crypto_service>.<algorithm_or_type> <attribute_name>
      int attrIndex = filterKey.indexOf(' ');

      if (attrIndex == -1) {
        // There is no attribute name in the filter.
        throw new InvalidParameterException("Invalid filter");
      } else {
        attrName = filterKey.substring(attrIndex + 1).trim();
        if (attrName.length() == 0) {
          // There is no attribute name in the filter.
          throw new InvalidParameterException("Invalid filter");
        }
      }

      // There must be an algorithm name in the filter.
      if ((attrIndex < algIndex) ||
          (algIndex == attrIndex - 1)) {
        throw new InvalidParameterException("Invalid filter");
      } else {
        algName = filterKey.substring(algIndex + 1, attrIndex);
      }
    }

    String[] result = new String[3];
    result[0] = serviceName;
    result[1] = algName;
    result[2] = attrName;

    return result;
  }

  /**
   * Returns a Set of Strings containing the names of all available
   * algorithms or types for the specified Java cryptographic service
   * (e.g., Signature, MessageDigest, Cipher, Mac, KeyStore). Returns
   * an empty Set if there is no provider that supports the
   * specified service or if serviceName is null. For a complete list
   * of Java cryptographic services, please see the
   * <a href="../../../technotes/guides/security/crypto/CryptoSpec.html">Java
   * Cryptography Architecture API Specification &amp; Reference</a>.
   * Note: the returned set is immutable.
   *
   * @param serviceName the name of the Java cryptographic service (e.g., Signature, MessageDigest,
   * Cipher, Mac, KeyStore). Note: this parameter is case-insensitive.
   * @return a Set of Strings containing the names of all available algorithms or types for the
   * specified Java cryptographic service or an empty set if no provider supports the specified
   * service.
   * @since 1.4
   **/
  public static Set<String> getAlgorithms(String serviceName) {

    if ((serviceName == null) || (serviceName.length() == 0) ||
        (serviceName.endsWith("."))) {
      return Collections.emptySet();
    }

    HashSet<String> result = new HashSet<>();
    Provider[] providers = Security.getProviders();

    for (int i = 0; i < providers.length; i++) {
      // Check the keys for each provider.
      for (Enumeration<Object> e = providers[i].keys();
          e.hasMoreElements(); ) {
        String currentKey =
            ((String) e.nextElement()).toUpperCase(Locale.ENGLISH);
        if (currentKey.startsWith(
            serviceName.toUpperCase(Locale.ENGLISH))) {
          // We should skip the currentKey if it contains a
          // whitespace. The reason is: such an entry in the
          // provider property contains attributes for the
          // implementation of an algorithm. We are only interested
          // in entries which lead to the implementation
          // classes.
          if (currentKey.indexOf(" ") < 0) {
            result.add(currentKey.substring(
                serviceName.length() + 1));
          }
        }
      }
    }
    return Collections.unmodifiableSet(result);
  }
}
